#include "triangle.h"
#include "color_shader.h"

#define EPSILON	0.000001


Triangle::Triangle(
	const glm::vec3& v1,
	const glm::vec3& v2,
	const glm::vec3& v3,
	const glm::vec3& col)
: _color(col)
{
	_vertices[0] = v1;
	_vertices[1] = v2;
	_vertices[2] = v3;
}


void Triangle::computeNormal()
{
	const glm::vec3& e1 = _vertices[1] - _vertices[0];
	const glm::vec3& e2 = _vertices[2] - _vertices[0];
	_normal = glm::normalize(glm::cross(e2, e1));
}


bool Triangle::rayIntersect(const Ray& ray, float* distance) const
{
	// Moller–Trumbore algorithm
	float det, inv_det, u, v, d;

	// Find vectors for two edges sharing v0
	const glm::vec3& e1 = _vertices[1] - _vertices[0];
	const glm::vec3& e2 = _vertices[2] - _vertices[0];
	// Begin calculating determinant - also used to calculate u parameter
	const glm::vec3& p = glm::cross(ray.getDirection(), e2);
	// If determinant is near zero, ray lies in plane of triangle
	det = glm::dot(e1, p);
	if (det > -EPSILON && det < EPSILON)
		// Not culling
		return false;
	inv_det = 1.f / det;

	// Calculate distance from v0 to ray origin
	const glm::vec3& t = ray.getStart() - _vertices[0];

	// Calculate u parameter and test bound
	u = glm::dot(t, p) * inv_det;
	if (u < 0.f || u > 1.f)
		// The intersection lies outside of the triangle
		return false;

	// Prepare to test v parameter
	const glm::vec3& q = glm::cross(t, e1);

	// Calculate v parameter and test bound
	v = glm::dot(ray.getDirection(), q) * inv_det;
	if (v < 0.f || u + v  > 1.f)
		// The intersection lies outside of the triangle
		return false;

	d = glm::dot(e2, q) * inv_det;
	// Ray intersection
	if (d > EPSILON) {
		*distance = d;
		return true;
	}
	// No hit, no win
	return false;
}


void Triangle::drawEdges(
	sdl::Surface* s, const Shader& vertex, const Shader& color)
{
	glm::vec3 p0, p1, p2;

	vertex.transform(_vertices[0], &p0);
	vertex.transform(_vertices[1], &p1);
	vertex.transform(_vertices[2], &p2);

	s->drawLine(p0, p1, color);
	s->drawLine(p1, p2, color);
	s->drawLine(p0, p2, color);
}


void Triangle::drawFilled(
	sdl::Surface* s, const Shader& vertex, const Shader& color)
{
	glm::vec3 p0, p1, p2;

	vertex.transform(_vertices[0], &p0);
	vertex.transform(_vertices[1], &p1);
	vertex.transform(_vertices[2], &p2);

	glm::vec3 *a, *b, *c;
	// Sort vertices so that a.y <= b.y <= c.y
	a = &p0; b = &p1; c = &p2;
	if (b->y < a->y) std::swap(a, b);
	if (c->y < b->y) std::swap(b, c);
	if (b->y < a->y) std::swap(a, b);

	// Calculate y deltas
	float ac_dy = (c->y != a->y) ? 1.f / (c->y - a->y) : 0;
	float ab_dy = (b->y != a->y) ? 1.f / (b->y - a->y) : 0;
	float bc_dy = (c->y != b->y) ? 1.f / (c->y - b->y) : 0;

	// Calculate interpolation steps
	glm::vec3 ac_step((c->x - a->x) * ac_dy, 1.f, (c->z - a->z) * ac_dy);
	glm::vec3 ab_step((b->x - a->x) * ab_dy, 1.f, (b->z - a->z) * ab_dy);
	glm::vec3 bc_step((c->x - b->x) * bc_dy, 1.f, (c->z - b->z) * bc_dy);

	glm::vec3 t(*a);
	// Draw top part of triangle
	while (a->y < b->y) {
		s->drawLine(*a, t, color);
		*a += ac_step;
		t += ab_step;
	}
	// Draw bottom part of triangle
	t = *b;
	while (a->y <= c->y) {
		s->drawLine(*a, t, color);
		*a += ac_step;
		t += bc_step;
	}
}


void Triangle::drawFilled(sdl::Surface* s, const Shader& vertex)
{
	drawFilled(s, vertex, ColorShader(_color));
}


void Triangle::scale(float factor)
{
	for (int i = 0; i < 3; i++) {
		_vertices[i] *= factor;
	}
}


void Triangle::move(const glm::vec3& relPos)
{
	for (int i = 0; i < 3; i++) {
		_vertices[i] += relPos;
	}
}


void Triangle::mirrorX()
{
	for (int i = 0; i < 3; i++) {
		_vertices[i].x = -_vertices[i].x;
	}
}


void Triangle::mirrorY()
{
	for (int i = 0; i < 3; i++) {
		_vertices[i].y = -_vertices[i].y;
	}
}

